// 策略模式-使用哈希表作为策略容器
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 先定义存放函数指针的哈希表

// 最大容量
#define MAX_HASH_SIZE 10

// 定义空键
#define NULL_KEY "NULL_STRATEGY"

/// @brief 策略模式哈希表
typedef struct
{
	int count;						   // 键值对个数
	char *key[MAX_HASH_SIZE];		   // 存放键的空间（存放策略名）
	void (*callback[MAX_HASH_SIZE])(); // 存放值的空间（存放函数指针，无参无返回值）
} StrategyHashMap;

/// @brief 创建一个策略哈希表
/// @return 哈希表对象
StrategyHashMap *createStrategyMap()
{
	StrategyHashMap *map = (StrategyHashMap *)malloc(sizeof(StrategyHashMap));
	map->count = 0;
	// 初始化空间
	int i;
	for (i = 0; i < MAX_HASH_SIZE; i++)
	{
		map->key[i] = (char *)calloc(64, sizeof(char));
		strcpy(map->key[i], NULL_KEY);
	}
	return map;
}

/// @brief 传入键计算地址（使用除数留余法）
/// @param key 键
/// @return 地址
int hash(char key[])
{
	// 统计字符串值
	char *p;
	int total = 0;
	for (p = key; *p != '\0'; p++)
	{
		total += *p;
	}
	return total % MAX_HASH_SIZE;
}

/// @brief 重新计算地址（开放定址法）
/// @param address 旧的地址
/// @return 重新计算的地址
int reAddress(int address)
{
	return (address + 1) % MAX_HASH_SIZE;
}

/// @brief 存入键值对到策略哈希表
/// @param map 哈希表
/// @param key 键（策略名）
/// @param strategy 策略回调函数（函数指针）
void putStrategyMap(StrategyHashMap *map, char key[], void (*strategy)())
{
	if (map->count == MAX_HASH_SIZE)
	{
		printf("哈希表满！\n");
		return;
	}
	// 计算地址
	int address = hash(key);
	// 若发生冲突则重新计算
	while (strcmp(map->key[address], NULL_KEY) != 0)
	{
		address = reAddress(address);
	}
	// 这时找到了空的地方，放入键值对
	strcpy(map->key[address], key);
	map->callback[address] = strategy;
	map->count++;
}

/// @brief 查找策略
/// @param map 哈希表
/// @param key 键（策略名）
/// @return 得到的策略函数指针
void (*getStrategyMap(StrategyHashMap *map, char key[]))()
{
	if (map->count == 0)
	{
		return NULL;
	}
	// 计算地址
	int address = hash(key);
	int originAddress = address;
	// 若查找到的键和当前键不符说明发生冲突，重新计算
	while (strcmp(map->key[address], key) != 0)
	{
		address = reAddress(address);
		// 若回到了原点或者找到的是空的键，说明这个键不存在
		if (address == originAddress || strcmp(map->key[address], NULL_KEY) == 0)
		{
			return NULL;
		}
	}
	// 此时找到键值对，返回值（函数指针）
	return map->callback[address];
}

/* ---------------------------下面是策略模式正文部分--------------------------- */

// 初始化策略容器

/// @brief 策略模式容器
static StrategyHashMap *container;

// 先罗列出全部的策略

void attack()
{
	// 普通攻击
	printf("[丢炸弹...]\n");
}

void normalSkill()
{
	// 元素战技：蹦蹦炸弹
	printf("弹起来吧！！！\n");
}

void specialSkill()
{
	// 元素爆发：轰轰火花
	printf("全都可以炸完！！！\n");
}

/// @brief 初始化策略容器，需要在主函数开始时执行
void init()
{
	container = createStrategyMap();
	putStrategyMap(container, "a", attack);
	putStrategyMap(container, "e", normalSkill);
	putStrategyMap(container, "q", specialSkill);
}

/// @brief 执行策略
/// @param name 策略名
void doStrategy(char name[])
{
	void (*strategy)() = getStrategyMap(container, name);
	if (strategy == NULL)
	{
		printf("没有该策略！终止！\n");
		return;
	}
	// 执行策略
	strategy();
}

int main()
{
	init();
	printf("请输入字母来执行角色不同的攻击方式：\n");
	printf("普通攻击：a; 元素战技：e; 元素爆发：q\n");
	char name[64];
	gets(name);
	doStrategy(name);
	return 0;
}